/*	Interactive Gravitational Simulator
	Project: IGS Visualization
	File: Application.cpp
	Author: Mike Bantegui <mbante2@gmail.com>, Hofstra University 
	Copyright (C) 2012 - 2013 Mike Bantegui

	This file is part of the IGS software package for simulating
	the N-Body problem in real-time. This was developed as part
	of a departmental honors thesis project at Hofstra University
	in the Spring 2012 semester.

	IGS is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	IGS is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "Application.h"
#include <cassert>
#include <iostream>
#include <SDL_opengl.h>

namespace IGS
{

// Constructs an instance of the Application
Application::Application() : LoopThread(NULL), IsSDLInitialized(false),
	IsRunning(false),  IsVisible(true), Screen(NULL), _Width(1280),
	_Height(720), FoV(60), Zmin(0.1), Zmax(300), DrawTimer(60, SDL_GetTicks, 1),
	LoopTimer(60, SDL_GetTicks, 1), KeyEvents() 
{
}

// Deconstructs the Application
Application::~Application()
{
}

// Whether the Application is currently running
bool Application::Running() const
{
	return IsRunning;
}

// Whether the Application is currently visible
bool Application::Visible() const
{
	return IsVisible;
}

// The width of the Application window
unsigned Application::Width() const
{
	return _Width;
}

// The height of the Application window
unsigned Application::Height() const
{
	return _Height;
}

// The aspect ratio of the Application window
double Application::Aspect() const
{
	return _Width / (double) _Height;
}

// Starts the Application
bool Application::Start(int argc, char **argv)
{
	assert(!IsSDLInitialized);
	assert(!IsRunning);

	if (!IsSDLInitialized && !InitializeSDL())
	{
		std::cerr << "Failed to initialize SDL" << std::endl;
		return false;
	}

	memset(KeyState, 0, sizeof(KeyState));

	// Initialize any app specific functionality
	if (Initialize(argc, argv))
	{
		DrawTimer.Reset();
		LoopTimer.Reset();

		IsRunning = true;
		LoopThread = SDL_CreateThread(LoopFunc, this);

		return true;
	}

	return false;
}

// Quits the application
void Application::Quit()
{
	assert(IsSDLInitialized);
	assert(IsRunning);

	if (IsRunning)
	{
		IsRunning = false;
		SDL_WaitThread(LoopThread, NULL);
		SDL_FreeSurface(Screen);
		Cleanup();
	}

	SDL_Quit();
	IsSDLInitialized = false;
}

// Continuously draw and poll for events until
// the application has a request to quit
void Application::Run()
{
	assert(IsSDLInitialized);

	DrawTimer.Reset();
	while (IsRunning)
	{
		if (IsVisible)
		{
			DrawTimer.Start();
			Draw();
			DrawTimer.Stop();
		}
		else
			SDL_Delay(16);

		PollEvents();
	}
}

// Initializes SDL 
bool Application::InitializeSDL()
{
	if (SDL_Init(SDL_INIT_VIDEO) == -1)
	{
		std::cerr << "SDL Initialization failed" << std::endl;
		std::cerr << SDL_GetError() << std::endl;
		return false;
	}

	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
	SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);

	SDL_ShowCursor(1);
	SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);

	Screen = SDL_SetVideoMode(_Width, _Height, 0, SDL_OPENGL);

	if (Screen == NULL)
	{
		std::cerr << "SDL_SetVideoMode failed" << std::endl;
		std::cerr << SDL_GetError() << std::endl;
		return false;
	}

	IsSDLInitialized = true;

	return true;
}

// Polls for any events. This filters for keyboard and mouse events.
// Certain events (SDL_QUIT, SDL_KEYDOWN, SDL_KEYUP) are filtered
// and handled here.
void Application::PollEvents()
{
	KeyEvents.clear();

	SDL_Event event;

	while (SDL_PollEvent(&event))
	{
		switch (event.type)
		{
		case SDL_QUIT:
			Quit();
			return;

		case SDL_KEYDOWN:
			KeyEvents.push_back(event);
			break;

		case SDL_KEYUP:
			KeyEvents.push_back(event);
			break;

		case SDL_ACTIVEEVENT:
			SDL_ActiveEvent active = *((SDL_ActiveEvent *) &event);
			if (active.state & SDL_APPACTIVE)
				IsVisible = !!active.gain;
			break;
		}
	}

	Mouse();
	Keys();
}

// Here for performance reasons, do not delete
bool Application::Initialize(int argc, char **argv)
{
	return true;
}

// Here for performance reasons, do not delete
void Application::Cleanup()
{
}

// Here for performance reasons, do not delete
void Application::Draw()
{
}

// Here for performance reasons, do not delete
void Application::Keys() 
{
}

// Here for performance reasons, do not delete
void Application::Mouse()
{
}

// Here for performance reasons, do not delete
void Application::Loop()
{
}

// Sets the application for 2D rendering
void Application::Set2DContext()
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	gluOrtho2D(0, _Width, _Height, 0);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

// Sets the application for 3D rendering
void Application::Set3DContext()
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	gluPerspective(FoV, Aspect(), Zmin, Zmax);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

// Continuously loops as long as the application is running
int LoopFunc(void *data)
{
	Application *app = (Application *) data;

	app->LoopTimer.Reset();
	while (app->IsRunning)
	{
		app->LoopTimer.Start();
		app->Loop();
		app->LoopTimer.Stop();
	}

	return 0;
}

}